//
//  NSObjectAdditions.m
//  Tint
//
//  Created by Kevin Wu on 2/22/12.
//  Copyright (c) 2012 Telligenty. All rights reserved.
//

#import "NSObjectAdditions.h"
#import <objc/runtime.h>
#import "NSOperationQueueAdditions.h"



@interface NSObject (TintPrivate)

/*
 * Sending messages basic implementation
 */
- (id)runSelector:(SEL)selector withObjects:(NSArray *)objects onMainThread:(BOOL)on;

- (id)runSelector:(SEL)selector onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 onMainThread:(BOOL)on;
- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 withObject:(id)p6 onMainThread:(BOOL)on;

@end


@implementation NSObject (TintPrivate)

#pragma mark - Sending messages basic implementation

- (id)runSelector:(SEL)selector withObjects:(NSArray *)objects onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        // set arguments
        for (int i=0, j=2; i<[objects count]; ++i,++j) {
            id object = [objects objectAtIndex:i];
            [invocation setArgument:&object atIndex:j];
        }
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        // return
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        [invocation setArgument:&p2 atIndex:3];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        [invocation setArgument:&p2 atIndex:3];
        [invocation setArgument:&p3 atIndex:4];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        [invocation setArgument:&p2 atIndex:3];
        [invocation setArgument:&p3 atIndex:4];
        [invocation setArgument:&p4 atIndex:5];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        [invocation setArgument:&p2 atIndex:3];
        [invocation setArgument:&p3 atIndex:4];
        [invocation setArgument:&p4 atIndex:5];
        [invocation setArgument:&p5 atIndex:6];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 withObject:(id)p6 onMainThread:(BOOL)on {
    if (![self respondsToSelector:selector]) {
        return nil;
    }
    
    NSMethodSignature *signature = [self methodSignatureForSelector:selector];
    if (signature) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
        [invocation setTarget:self];
        [invocation setSelector:selector];
        
        [invocation setArgument:&p1 atIndex:2];
        [invocation setArgument:&p2 atIndex:3];
        [invocation setArgument:&p3 atIndex:4];
        [invocation setArgument:&p4 atIndex:5];
        [invocation setArgument:&p5 atIndex:6];
        [invocation setArgument:&p6 atIndex:7];
        
        if (on) {
            [invocation performSelectorOnMainThread:@selector(invoke) withObject:nil waitUntilDone:YES];
        } else {
            [invocation invoke];
        }
        
        if ([signature methodReturnLength]) {
            id ret = nil;
            [invocation getReturnValue:&ret];
            return ret;
        }
    }
    return nil;
}

@end




@implementation NSObject (Tint)


#pragma mark - Creating and Class

+ (id)make {
    return [[[self alloc] init] autorelease];
}

+ (NSString *)classString {
    return NSStringFromClass([self class]);
}

- (NSString *)classString {
    return NSStringFromClass([self class]);
}



#pragma mark - Associate object

- (void)associateValue:(id)value withKey:(void *)key {
	objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_RETAIN);
}

- (void)weaklyAssociateValue:(id)value withKey:(void *)key {
	objc_setAssociatedObject(self, key, value, OBJC_ASSOCIATION_ASSIGN);
}

- (id)associatedValueForKey:(void *)key {
	return objc_getAssociatedObject(self, key);
}


#pragma mark - KVC

- (BOOL)isValueForKeyPath:(NSString *)keyPath equalToValue:(id)value {
    return [[self valueForKeyPath:keyPath] isEqual:value];
}

- (BOOL)isValueForKeyPath:(NSString *)keyPath identicalToValue:(id)value {
    return ([self valueForKeyPath:keyPath] == value);
}

+ (NSArray *)propertyNames {
    
    NSMutableArray *names = [NSMutableArray array];
    
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(self, &count);
    for (int i=0; i<count; ++i) {
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(properties[i])];
        [names addObject:propertyName];
    }
    
    if ([names count]>0)
        return [[NSArray alloc] initWithArray:names];
    
    return nil;
}

+ (NSDictionary *)propertyAttributes {
    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    
    unsigned int count = 0;
    objc_property_t *properties = class_copyPropertyList(self, &count);
    for (int i=0; i<count; ++i) {
        objc_property_t property = properties[i];
        NSString *name = [NSString stringWithUTF8String:property_getName(property)];
        NSString *attribute = [NSString stringWithUTF8String:property_getAttributes(property)];
        [attributes setObject:attribute forKey:name];
    }
    
    if ([attributes count]>0)
        return [[NSDictionary alloc] initWithDictionary:attributes];
    
    return nil;
}

+ (NSString *)attributeForProperty:(NSString *)property {
    NSDictionary *attributes = [self propertyAttributes];
    return [attributes objectForKey:property];
}



#pragma mark - Sending messages

- (id)runSelector:(SEL)selector withObjects:(NSArray *)objects {
    return [self runSelector:selector withObjects:objects onMainThread:NO];
}

- (id)runSelectorOnMainThread:(SEL)selector withObjects:(NSArray *)objects {
    return [self runSelector:selector withObjects:objects onMainThread:YES];
}


- (NSInvocationOperation *)runSelectorInBackgroundQueue:(SEL)selector withObject:(id)p1 {
	NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:selector object:p1];
    [[NSOperationQueue sharedObject] addOperation:operation];
	return [operation autorelease];  
}

- (NSInvocationOperation *)runSelectorInBackgroundQueue:(SEL)selector withObject:(id)p1 dependencies:(NSArray *)dependencies priority:(NSOperationQueuePriority)priority {
    
	NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:selector object:p1];
    [operation setQueuePriority:priority];
    for (NSOperation *dependency in dependencies) {
        [operation addDependency:dependency]; 
    }
    [[NSOperationQueue sharedObject] addOperation:operation];
	return [operation autorelease];  
}


- (id)runSelector:(SEL)selector {
    return [self runSelector:selector onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 {
    return [self runSelector:selector withObject:p1 onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 {
    return [self runSelector:selector withObject:p1 withObject:p2 onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 withObject:p5 onMainThread:NO];
}

- (id)runSelector:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 withObject:(id)p6 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 withObject:p5 withObject:p6 onMainThread:NO];
}


- (id)runSelectorOnMainThread:(SEL)selector {
    return [self runSelector:selector onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 {
    return [self runSelector:selector withObject:p1 onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 withObject:(id)p2 {
    return [self runSelector:selector withObject:p1 withObject:p2 onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 withObject:p5 onMainThread:YES];
}

- (id)runSelectorOnMainThread:(SEL)selector withObject:(id)p1 withObject:(id)p2 withObject:(id)p3 withObject:(id)p4 withObject:(id)p5 withObject:(id)p6 {
    return [self runSelector:selector withObject:p1 withObject:p2 withObject:p3 withObject:p4 withObject:p5 withObject:p6 onMainThread:YES];
}

@end
